#!/usr/bin/python
#
# Copyright (C) 2008 rambla.be
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


"""Contains classes representing RAWS elements.

  Extends Atom classes to add RAWS specific elements.
"""
import os
try:
  from xml.etree import cElementTree as ElementTree
except ImportError:
  try:
    import cElementTree as ElementTree
  except ImportError:
    from elementtree import ElementTree
import ratom

# XML namespaces which are often used in Raws entities.
RAWS_NAMESPACE = 'http://rambla.be/raws/ns-metadata/1.0'

# state machine
REQUEST_RECEIVED = 1 # door RATS App na ontvangen van request
IMPORT_IN_PROGRESS = 2 # door ImportDaemon, wanneer deze begint een nieuwe request te behandelen
IMPORT_SUCCEEDED = 3 # door ImportDaemon, wanneer deze de nieuwe request heeft behandeld
TRANS_IN_PROGRESS = 4 # door TransDaemon bij het starten van de transcoding
TRANS_SUCCEEDED = 5 # door TransDaemon bij het beeindigen van de transcoding
EXPORT_IN_PROGRESS = 6 # door ExportDaemon bij het starten van de export
FINISHED = 7 # door ExportDaemon bij het beeindigen van de export
IMPORT_FAILED = 8 # door ImportDaemon bij het falen van de export
TRANS_FAILED = 9 # door TransDaemon bij het falen van de transcoding
EXPORT_FAILED = 10 # door ExportDaemon bij het falen van de export 

class RawscException(Exception):
  pass

class MissingRequiredParameters(RawscException):
  pass


class Query(dict):
    """Constructs a query URL to be used in GET requests

    Url parameters are created by adding key-value pairs to this object as a
    dict. For example, to add &max-results=25 to the URL do
    my_query['max-results'] = 25
    """

    def __init__(self, feed=None, params=None):
      """Constructor for Query

      Args:
        feed: str (optional) The path for the feed (Examples:
            '/base/feeds/snippets' or 'calendar/feeds/jo@gmail.com/private/full'
        params: dict (optional) Parameter value string pairs which become URL
            params when translated to a URI. These parameters are added to the
            query's items (key-value pairs).
      """
      self.feed = feed
      if isinstance(params, dict):
          for param in params:
              self[param] = params[param]

    def _GetTextQuery(self):
      if 'q' in self.keys():
          return self['q']
      else:
          return None

    def _SetTextQuery(self, query):
      self['q'] = query

      text_query = property(_GetTextQuery, _SetTextQuery,
        doc="""The feed query's q parameter""")

    def ToUri(self):
      q_feed = self.feed or ''
      return ratom.service.BuildUri(q_feed, self)

    def __str__(self):
      return self.ToUri()


class MediaSource(object):
  """Raws Entries can refer to media sources, so this class provides a
  place to store references to these objects along with some metadata.
  """
  
  def __init__(self, file_handle=None, content_type=None, content_length=None,
      file_path=None, file_name=None, svr_filename = None):
    """Creates an object of type MediaSource.

    Args:
      file_handle: A file handle pointing to the file to be encapsulated in the
                   MediaSource
      content_type: string The MIME type of the file. Required if a file_handle
                    is given.
      content_length: int The size of the file. Required if a file_handle is
                      given.
      file_path: string (optional) A full path name to the file. Used in
                    place of a file_handle.
      file_name: string The name of the file without any path information.
                 Required if a file_handle is given.
    """
    self.file_handle = file_handle
    self.content_type = content_type
    self.content_length = content_length
    self.file_name = file_name
    self.svr_filename = svr_filename
    self.file_path = file_path
    
    if (file_handle is None and file_path is not None):
        self.setFile(file_path, content_type)

    if not self.svr_filename:
        self.svr_filename = self.file_name


  def setFile(self, file_name, content_type):
    """A helper function which can create a file handle from a given filename
    and set the content type and length all at once.

    Args:
      file_name: string The path and file name to the file containing the media
      content_type: string A MIME type representing the type of the media
    """
    self.file_handle = open(file_name, 'rb')
    self.content_type = content_type
    self.content_length = os.path.getsize(file_name)
    self.file_name = os.path.basename(file_name)

  def writeFile(self, file_path):
    # can not write if no path and handle
    if not file_path or not self.file_handle:
        return False
    self.file_path = file_path
    fd = open(file_path, 'wb')
    fd.write(self.file_handle.read())  
    fd.close()
    return True
    
     

class RawsParams(ratom.AtomBase):
    """ Base class for the RAWS params element. """
    _tag = 'params'
    _namespace = RAWS_NAMESPACE
    _children = {}
    _attributes = {}
    # _children['{%s}name' % RAWS_NAMESPACE] = ('name', Name)

    def __init__(self, extension_elements=None, extension_attributes=None):
      """Constructor for Content

      Args:
        content_type: string
        src: string
        text: str The text data in the this element
        extension_elements: list A  list of ExtensionElement instances
        extension_attributes: dict A dictionary of attribute value string pairs
      """
      self.type = "application/xml"
      self.src = None
      self.text = None
      self.extension_elements = extension_elements or []
      self.extension_attributes = extension_attributes or {}

def ParamsFromString(xml_string):
  return ratom.CreateClassFromXMLString(Value, xml_string)


class RawsContent(ratom.AtomBase):
    """ Base class for the atom:content element for RAWS entries. """

    _tag = 'content'
    _namespace = ratom.ATOM_NAMESPACE
    _children = ratom.AtomBase._children.copy()
    _attributes = ratom.AtomBase._attributes.copy()
    # _children['{%s}params' % (RAWS_NAMESPACE)] = ('params', Params)

    def __init__(self, extension_elements=None, extension_attributes=None):
      """Constructor for Content

      Args:
        content_type: string
        src: string
        text: str The text data in the this element
        extension_elements: list A  list of ExtensionElement instances
        extension_attributes: dict A dictionary of attribute value string pairs
      """
      self.type = "application/xml"
      self.src = None
      self.text = None
      self.extension_elements = extension_elements or []
      self.extension_attributes = extension_attributes or {}

def ContentFromString(xml_string):
    return CreateClassFromXMLString(Content, xml_string)


class LinkFinder(ratom.LinkFinder):
  """An "interface" providing methods to find link elements

  RAWS Entry elements often contain multiple links which differ in the rel
  attribute or content type. Often, developers are interested in a specific
  type of link so this class provides methods to find specific classes of
  links.

  This class is used as a mixin in RAWS entries.
  """

  def GetSelfLink(self):
    """Find the first link with rel set to 'self'

    Returns:
      An ratom.Link or none if none of the links had rel equal to 'self'
    """
    for a_link in self.link:
      if a_link.rel == 'self':
        return a_link
    return None

  def GetEditLink(self):
    for a_link in self.link:
      if a_link.rel == 'edit':
        return a_link
    return None

  # def GetEditMediaLink(self):
  #   """The Picasa API mistakenly returns media-edit rather than edit-media, but
  #   this may change soon.
  #   """
  #   for a_link in self.link:
  #     if a_link.rel == 'edit-media':
  #       return a_link
  #     if a_link.rel == 'media-edit':
  #       return a_link
  #   return None

  def GetHtmlLink(self):
    """Find the first link with rel of alternate and type of text/html

    Returns:
      An ratom.Link or None if no links matched
    """
    for a_link in self.link:
      if a_link.rel == 'alternate' and a_link.type == 'text/html':
        return a_link
    return None

  # def GetPostLink(self):
  #   """Get a link containing the POST target URL.
  # 
  #   The POST target URL is used to insert new entries.
  # 
  #   Returns:
  #     A link object with a rel matching the POST type.
  #   """
  #   for a_link in self.link:
  #     if a_link.rel == 'http://schemas.google.com/g/2005#post':
  #       return a_link
  #   return None
  # 
  # def GetAclLink(self):
  #   for a_link in self.link:
  #     if a_link.rel == 'http://schemas.google.com/acl/2007#accessControlList':
  #       return a_link
  #   return None
  # 
  # def GetFeedLink(self):
  #   for a_link in self.link:
  #     if a_link.rel == 'http://schemas.google.com/g/2005#feed':
  #       return a_link
  #   return None
  # 
  # def GetNextLink(self):
  #   for a_link in self.link:
  #     if a_link.rel == 'next':
  #       return a_link
  #   return None


class RawsEntry(ratom.Entry, LinkFinder):
  """Extends Atom Entry to provide data processing"""

  _tag = ratom.Entry._tag
  _namespace = ratom.Entry._namespace
  _children = ratom.Entry._children.copy()
  _attributes = ratom.Entry._attributes.copy()
  # _children['{%s}content' % ratom.ATOM_NAMESPACE] = ('content', RawsContent)

  def __init__(self, author=None, category=None, atom_id=None, link=None, published=None, title=None, updated=None, content = None, extension_elements=None, extension_attributes=None):
      ratom.Entry.__init__(self, author=author, category=category, atom_id=atom_id, link=link, published=published, title=title, updated=updated,
                              extension_elements=extension_elements, extension_attributes=extension_attributes)
 

  def __GetId(self):
    return self.__id

  # This method was created to strip the unwanted whitespace from the id's 
  # text node.
  def __SetId(self, id):
    self.__id = id
    if id is not None:
      self.__id.text = id.text.strip()

  id = property(__GetId, __SetId)
  
  def IsMedia(self):
    """Determines whether or not an entry is a Raws Media entry.
    """
    if (self.GetEditMediaLink()):
      return True
    else:
      return False
  
  def GetMediaURL(self):
    """Returns the URL to the media content, if the entry is a media entry.
    Otherwise returns None.
    """
    if not self.IsMedia():
      return None
    else:
      return self.content.src


def RawsEntryFromString(xml_string):
  """Creates a new RawsEntry instance given a string of XML."""
  return ratom.CreateClassFromXMLString(RawsEntry, xml_string)



class RawsFeed(ratom.Feed, LinkFinder):
  """A Feed from a Raws service"""

  _tag = 'feed'
  _namespace = ratom.ATOM_NAMESPACE
  _children = ratom.Feed._children.copy()
  _attributes = ratom.Feed._attributes.copy()
  
  # Add a conversion rule for atom:entry to make it into a Raws
  # Entry.
  _children['{%s}entry' % ratom.ATOM_NAMESPACE] = ('entry', [RawsEntry])

  def __GetId(self):
    return self.__id

  def __SetId(self, id):
    self.__id = id
    if id is not None:
      self.__id.text = id.text.strip()

  id = property(__GetId, __SetId)

  def __GetGenerator(self):
    return self.__generator

  def __SetGenerator(self, generator):
    self.__generator = generator
    if generator is not None:
      self.__generator.text = generator.text.strip()

  generator = property(__GetGenerator, __SetGenerator)

  def __init__(self, author=None, category=None, contributor=None,
      generator=None, icon=None, atom_id=None, link=None, logo=None, 
      rights=None, subtitle=None, title=None, updated=None, entry=None, 
      total_results=None, start_index=None, items_per_page=None,
      extension_elements=None, extension_attributes=None, text=None):
    """Constructor for Source
    
    Args:
      author: list (optional) A list of Author instances which belong to this
          class.
      category: list (optional) A list of Category instances
      contributor: list (optional) A list on Contributor instances
      generator: Generator (optional) 
      icon: Icon (optional) 
      id: Id (optional) The entry's Id element
      link: list (optional) A list of Link instances
      logo: Logo (optional) 
      rights: Rights (optional) The entry's Rights element
      subtitle: Subtitle (optional) The entry's subtitle element
      title: Title (optional) the entry's title element
      updated: Updated (optional) the entry's updated element
      entry: list (optional) A list of the Entry instances contained in the 
          feed.
      text: String (optional) The text contents of the element. This is the 
          contents of the Entry's XML text node. 
          (Example: <foo>This is the text</foo>)
      extension_elements: list (optional) A list of ExtensionElement instances
          which are children of this element.
      extension_attributes: dict (optional) A dictionary of strings which are 
          the values for additional XML attributes of this element.
    """

    self.author = author or []
    self.category = category or []
    self.contributor = contributor or []
    self.generator = generator
    self.icon = icon
    self.id = atom_id
    self.link = link or []
    self.logo = logo
    self.rights = rights
    self.subtitle = subtitle
    self.title = title
    self.updated = updated
    self.entry = entry or []
    self.total_results = total_results
    self.start_index = start_index
    self.items_per_page = items_per_page
    self.text = text
    self.extension_elements = extension_elements or []
    self.extension_attributes = extension_attributes or {}

def RawsFeedFromString(xml_string):
  return ratom.CreateClassFromXMLString(RawsFeed, xml_string)


class RawsBaseElement(ratom.AtomBase):
    """Base class for elements in the RAWS_NAMESPACE. To add new elements,
    you only need to add the element tag name to self._tag
    """
    _tag = ''
    _namespace = RAWS_NAMESPACE
    _children = ratom.AtomBase._children.copy()
    _attributes = ratom.AtomBase._attributes.copy()

    def __init__(self, name=None, extension_elements=None,
        extension_attributes=None, text=None):
      self.name = name
      self.text = text
      self.extension_elements = extension_elements or []
      self.extension_attributes = extension_attributes or {}
    #def __str__(self):
      #return str(self.text)
    #def __unicode__(self):
      #return unicode(self.text)
    def __int__(self):
      return int(self.text)
    def bool(self):
      return self.text == 'true'


def get_last_link(feed):
    url = None
    for l in feed.link:
        if l.rel == "last":
            url = l.href
            break
    return url


def get_next_link(feed):
    url = None
    for l in feed.link:
        if l.rel == "next":
            url = l.href
            break
    return url

def get_enclosure_link(entry):
    url = None
    for l in entry.link:
        if l.rel == "enclosure":
            url = l.href
            break
    return url

def get_nr_from_url(url):
    """ Extracts the value of the 'page' querystr arg from a given url. """
    nr = None
    pos = url.find("page=")
    if pos != -1:
        pos = pos + 5 # -> should point to first page digit
        separator_index = url.find(";", pos)
        if separator_index != -1:
            nr = url[pos:separator_index]
        else:
            nr = url[pos:]
        try:
            nr = int(nr)
        except ValueError, e:
            raise rawsc.RawscException('Failed to convert last page number to integer (url = %s, nr = %s).' % (str(url), str(nr)))
    return nr
    
def get_last_nr(feed):
    nr = None
    url = get_last_link(feed)
    if url:
        nr = get_nr_from_url(url)
    return nr
        
def get_next_nr(feed):
    nr = None
    url = get_next_link(feed)
    if url:
        nr = get_nr_from_url(url)
    return nr
